Como utilizar un rotary encoder con un microcontrolador AVR.
En este caso usaremos un SIQ-02FVS3 que está integrado en una pantalla ssd1306
SIQ-02FVS3 datasheet
El encoder que estamos usando tiene 4 puertos, uno para tierra, dos para la rotación del encoder y otro para la pulsación de la rueda.
En nuestro caso conectamos los puertos de rotación (A y B) al los pines PB2 y PB3 y el puerto de pulsación al puerto PB1.
Para detectar los cambios en el encoder usaremos las interrupciones del microcontrolador, activamos las interrupciones para los puertos que usaremos:
PCICR |= (1 << PCIE0);
PCMSK0 |= (1 << PCINT1) | (1 << PCINT2) | (1 << PCINT3);
sei();
Ahora para detectar la rotación tenemos que tener en cuenta como funciona el encoder, al girar la rueda del encoder se genera una señal como la siguiente en los puertos A y B:
Al girar por ejemplo en el sentido de las agujas del reloj primero se genera una señal en A y despues en B, como podemos observar hay un momento en el que ambas señales están solapadas, usaremos esto para detectar en que sentido está girando el encoder.
Lo que hacemos es detectar que señal se produce primero, A o B, y despues cuando están solapadas decidimos en que sentido está girando el encoder basándonos en la primera señal que obtuvimos.
El código que usamos en la función ISR() es el siguiente:
volatile uint8_t up_value = 0;
volatile uint8_t down_value = 0;
volatile uint8_t push_value = 0;
volatile uint8_t active_before = 0;
ISR(PCINT0_vect) {
if(bit_is_clear(PINB, PB2) && bit_is_set(PINB, PB3)){
active_before = 1;
}
else if (bit_is_set(PINB, PB2) && bit_is_clear(PINB, PB3)){
active_before = 2;
}
else if (bit_is_clear(PINB, PB2) && bit_is_clear(PINB, PB3)){
if(active_before == 1)
up_value++;
else if(active_before == 2)
down_value++;
}
if(bit_is_clear(PINB, PB1)){
push_value++;
}
}
En este código active_before lo usamos para almacenar que señal se ha producido primero, 1 para A y 2 para B.
En las últimas líneas se hace la comprobación del puerto push del encoder (PB1 en el microcontrolador), para saber si se ha pulsado la rueda, en este caso solo es un contador ya que por las pruebas que he hecho así funciona bien, pero puede ser que en algún caso se den casos de rebote al pulsar la rueda, en ese caso tendríamos que usar alguna técnica para evitar el rebote.
El siguiente código muestra el ejemplo completo para usar con la pantalla ssd1306:
NOTA: es importante no poner el código que pinta la pantalla dentro de la ISR(), ya que pintar la pantalla es bastante lento y esto puede generar que al girar muy rapido el encoder no se lean correctamente todas las entradas por la latencia generada al pintar la pantalla.
#include <avr/power.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "lcd.h"
#define NUMBER_DIGITS 2
volatile uint8_t up_value = 0;
volatile uint8_t down_value = 0;
volatile uint8_t push_value = 0;
volatile uint8_t active_before = 0;
void print_number(uint16_t number, uint8_t digits){
char number_string[4] = {0};
uint8_t i = digits;
while(number > 0){
number_string[i-1] = number%10;
number = number / 10;
i--;
}
for(i=0; i<digits; i++){
lcd_putc(48 + number_string[i]);
}
}
ISR(PCINT0_vect) {
if(bit_is_clear(PINB, PB2) && bit_is_set(PINB, PB3)){
active_before = 1;
}
else if (bit_is_set(PINB, PB2) && bit_is_clear(PINB, PB3)){
active_before = 2;
}
else if (bit_is_clear(PINB, PB2) && bit_is_clear(PINB, PB3)){
if(active_before == 1)
up_value++;
else if(active_before == 2)
down_value++;
}
if(bit_is_clear(PINB, PB1)){
push_value++;
}
}
void init_interrupts(){
PCICR |= (1 << PCIE0);
PCMSK0 |= (1 << PCINT1) | (1 << PCINT2) | (1 << PCINT3);
sei();
}
int main(void){
clock_prescale_set(clock_div_1);
PORTB = 0b00000111;
lcd_init(LCD_DISP_ON);
lcd_puts("Rotary test");
init_interrupts();
while(1){
lcd_gotoxy(0,3);
lcd_puts("UP:");
print_number(up_value, NUMBER_DIGITS);
lcd_gotoxy(0,4);
lcd_puts("DOWN:");
print_number(down_value, NUMBER_DIGITS);
lcd_gotoxy(0,5);
lcd_puts("PUSH:");
print_number(push_value, NUMBER_DIGITS);
_delay_ms(16);
}
return 0;
}
AVR | Rotary